﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using VSDBM.Code.dao;
using VSDBM.Code.dto;
using VSDBM.Code.functions;
using System.Text;

/// <summary>
/// Summary description for SmithWatermanMemory
/// </summary>
public class SmithWatermanMemory
{
    public float penaltyGapExist { get; set; }
    public float penaltyGapExtend { get; set; }
    public String databaseSequence { get; set; }
    public String querrySequence { get; set; }
    public SubstitutionMatrix substitutionMatrix { get; set; }
    public float[] DatabaseScoreArray;
    public float[] QuerryScoreArray;
    public float highScore = 0;
    public int endAlignmentDatabase = 0;
    public int endAlignmentQuery = 0;
    public int startAlignmentDatabase = 0;
    public int startAlignmentQuery = 0;
    public PairwiseAlignmentDB alignment;
    public String[,] matrix;
    public bool shouldtraceback;

    public SmithWatermanMemory(Sequence database, Sequence querry, SubstitutionMatrix substitutionMatrix, bool shouldtraceback)

	{
        if (database.sequence.Length < 1 || querry.sequence.Length < 1) throw new Exception("Error in Smith-Waterman-Gotoh dynamic programming. Length of database sequence or query sequence has to be at least 1 bp");


        this.penaltyGapExist = substitutionMatrix.gapExistPenalty;
        this.penaltyGapExtend = substitutionMatrix.gapExtendPenalty;
        this.databaseSequence = database.sequence;
        this.querrySequence = querry.sequence;
        this.substitutionMatrix = substitutionMatrix;
        this.shouldtraceback = shouldtraceback;


        //test other matrixes if u DARE!.. =D(well.. i did).


        createMatrix();
	}

    protected float Max(float first, float second)
    {
        if (first > second) return first;
        else return second;
    }
    public void createMatrix()
    {
        //CODE FOR LAST_MOVE:
        // 1 - Diagonal
        // 2 - Insertion
        // 3 - Deletion
        // 4 - None

       //initialize the matrix
         matrix = new String[databaseSequence.Length+1, querrySequence.Length+1];

        //initialize the Gotoh arrays - containing the score of the best previous deletion
        this.DatabaseScoreArray = new float[databaseSequence.Length + 1];
        this.QuerryScoreArray = new float[querrySequence.Length + 1];

        //First set all values in the horizontal (database) to zero(first line)
        for (int i = 0; i < databaseSequence.Length + 1; i++)
        {
            matrix[i, 0] = "0,4";
            DatabaseScoreArray[i] = 0 - penaltyGapExist;
        }

        //Second set all values in the vertical (query) to zero(first column)
        for (int j = 0; j < querrySequence.Length + 1; j++)
        {
            matrix[0, j] = "0,4";
            QuerryScoreArray[j] = 0 - penaltyGapExist;
        }



        //Go down use dimension of the query
        for (int j = 1; j < querrySequence.Length + 1; j++)
        {
            for (int i = 1; i < databaseSequence.Length + 1; i++)
            {
                //i=database sequence in the horizontal
                //j=query sequence in the vertical


                //the database sequence is in the horizontal, the query in the vertical axis of the matrix
                //Diagonal score is the previous score and in addition the similarityValue;
                float scoredi = (float)Convert.ToDouble(matrix[i - 1, j - 1].Split(',')[0]);
                float scoreDiagonal = scoredi + substitutionMatrix.GetSimilarityValue(databaseSequence[i - 1], querrySequence[j - 1]);

                //Find the highest scoring insertion, testing all matrix to the upper side;
                float downScoreInsertion = 0;
                float scorei = (float)Convert.ToDouble(matrix[i, j - 1].Split(',')[0]);
                downScoreInsertion = this.Max(scorei - penaltyGapExist, DatabaseScoreArray[i] - penaltyGapExtend);

                DatabaseScoreArray[i] = downScoreInsertion;


                //Find the highest scroing deletion, testing all matrix entries to the left side
                float scorede = (float)Convert.ToDouble(matrix[i - 1, j].Split(',')[0]);
                float rightScoreDeletion = 0;
                rightScoreDeletion = Math.Max(scoredi - penaltyGapExist, QuerryScoreArray[j] - penaltyGapExtend);
                QuerryScoreArray[j] = rightScoreDeletion;

                Object[] nowcell = GetMaximumPosition(scoreDiagonal, downScoreInsertion, rightScoreDeletion, 0);
                float nowScore = (float)nowcell[0];
                int nowLastMove = (int)nowcell[1];

               
                String scorenow = nowScore.ToString() + "," + nowLastMove.ToString();
                if(scorenow.Length > ((querrySequence.Length*substitutionMatrix.highestScore).ToString().Length)+2)
                {
                    String nada = "Tamanho da célula é muito pequena.";
                }
                matrix[i, j] = scorenow;



                //Updating the highest scoring matrix entry
                if (nowScore > highScore)
                {
                    //new highscore
                    highScore = nowScore;
                    endAlignmentDatabase = i;
                    endAlignmentQuery = j;
                }



            }
        }
        if (shouldtraceback)
        {
            Traceback();
        }
    }

    public void Traceback()
    {

        //Actual position in the matrix; D..database; Q..query
        int posD = this.endAlignmentDatabase;
        int posQ = this.endAlignmentQuery;
        StringBuilder databaseBuilder = new StringBuilder();
        StringBuilder queryBuilder = new StringBuilder();

        while (Convert.ToInt32(matrix[posD,posQ].Split(',')[1]) != 4)
        {

            //Move to the next character
            switch (Convert.ToInt32(matrix[posD, posQ].Split(',')[1]))
            {
                case 1: //Diagonal
                    databaseBuilder.Append(databaseSequence[posD - 1]);
                    queryBuilder.Append(querrySequence[posQ - 1]);
                    posD--; posQ--;

                    break;
                case 3://Deletion
                    int startPosD = posD;
                    do
                    {
                        databaseBuilder.Append(databaseSequence[posD - 1]);
                        queryBuilder.Append('-');
                        posD--;


                    }
                    while (Math.Round((float)Convert.ToDouble(matrix[posD, posQ].Split(',')[0]), 4) != Math.Round(((float)Convert.ToDouble(matrix[startPosD, posQ].Split(',')[0]) + penaltyGapExist + penaltyGapExtend * (startPosD - posD - 1)), 4));

                    break;
                case 2://Insertion
                    int startPosQ = posQ;
                    do
                    {
                        databaseBuilder.Append('-');
                        queryBuilder.Append(querrySequence[posQ - 1]);
                        posQ--;


                    }
                    while (Math.Round((float)Convert.ToDouble(matrix[posD, posQ].Split(',')[0]), 2) != Math.Round(((float)Convert.ToDouble(matrix[posD, startPosQ].Split(',')[0]) + penaltyGapExist + penaltyGapExtend * (startPosQ - posQ - 1)), 2));

                    break;
                default: throw new InvalidOperationException("none of the last move codes was achived");
            }


        }
        if (endAlignmentDatabase > 0 && endAlignmentQuery > 0)
        {
            startAlignmentDatabase = posD + 1;
            startAlignmentQuery = posQ + 1;
            alignment = new PairwiseAlignmentDB(databaseBuilder.ToString(), queryBuilder.ToString());
        }

        

    }

    protected static Object[] GetMaximumPosition(float scoreDiagonal, float scoreInsertion, float scoreDeletion, float scoreNone)
    {
        Object[] cell = new Object[2];

        if (scoreDiagonal > scoreNone)
        {
            //exclude scoreNone

            if (scoreDiagonal >= scoreInsertion)
            {
                //exclude scoreNone & scoreInsertion

                if (scoreDiagonal >= scoreDeletion)
                {
                    //exclude scoreNone & scoreInsertion & scoreDeletion => DIAGONAL

                    cell[0] = scoreDiagonal;
                    cell[1] = 1;
                }
                else
                {
                    //exclude scoreNone & scoreInsertion & scoreDiagonal => DELETION
                    cell[0] = scoreDeletion;
                    cell[1] = 3;

                }
            }
            else
            {
                //exclude scoreNone & scoreDiagonal


                if (scoreInsertion > scoreDeletion)
                {
                    //exclude scoreNone & scoreDiagonal & scoreDeletion => INSERTION
                    cell[0] = scoreInsertion;
                    cell[1] = 2;
                }
                else
                {
                    //exclude scoreNone &scoreDiagonal & scoreInsertion => DELETION
                    cell[0] = scoreDeletion;
                    cell[1] = 3;


                }
            }
        }
        else
        {
            //exclude scoreDiagonal
            if (scoreInsertion > scoreNone)
            {
                //exclude scoreDiagonal & scoreNone

                if (scoreInsertion > scoreDeletion)
                {
                    //exclude scoreDiagonal & scoreNone & scoreDeletion => INSERTION
                    cell[0] = scoreInsertion;
                    cell[1] = 2;
                }
                else
                {
                    //exclude scoreDiagonal & scoreNone & scoreInsertion => DELETION
                    cell[0] = scoreDeletion;
                    cell[1] = 3;
                }
            }
            else
            {
                //exclude scoreDiagonal & scoreInsertion
                if (scoreDeletion > scoreNone)
                {
                    //exclude scoreDiagonal & scoreInsertion & scoreNone => DELETION
                    cell[0] = scoreDeletion;
                    cell[1] = 3;
                }
                else
                {
                    //exclude scoreDiagonal & scoreInsertion & scoreDeletion =>NONE
                    cell[0] = scoreNone;
                    cell[1] = 4;
                }
            }


        }


        return cell; //UFA!!
    }

}